import { Events, REST, Routes } from 'discord.js';
import bot from '../../bot';
import { logger } from '../../util/logger';
import BotConfigModel from '../../util/db/models/botConfig';
import goose from '../../util/db/goose';
import { commandRegistry } from '../commandRegistry';
import { createHash } from 'crypto';
import { TOKEN } from '../../util/env';
import registerCommandsInGuild, {
  registerCommandsGlobally,
} from '../../util/registerCommands';
import { isDev } from '../../util/args';
import { Dearray } from '../../util/types';

bot.on(Events.ClientReady, async () => {
  logger.debug(
    {
      username: bot.user?.username,
      displayName: bot.user?.displayName,
      id: bot.user?.id,
      invite: `https://discord.com/api/oauth2/authorize?client_id=${bot.user?.id}&permissions=8&scope=bot%20applications.commands`,
    },
    `Logged in as ${bot.user?.username}`,
  );
  await goose;
  const botConfig =
    (await BotConfigModel.findOne({
      botId: bot.user?.id ?? '-1',
    })) ?? new BotConfigModel();
  botConfig.botId = bot.user?.id ?? '-1';
  if (!botConfig.lastCommandHash) botConfig.lastCommandHash = '---';
  const commands = commandRegistry.map(v => ({
    ...v._builder.export().toJSON(),
    restrictedGuilds: v._builder.guildRestrictions,
  }));
  const commandHash = createHash('SHA512')
    .update(
      commands
        .map(v => JSON.stringify(v))
        .sort()
        .join('++'),
    )
    .digest('hex');
  if (commandHash !== botConfig.lastCommandHash) {
    logger.info(
      `Detected different command hash! Re-registering ${commandRegistry.size} Slash commands...`,
    );
    const rest = new REST().setToken(TOKEN ?? bot.token!);
    if (!bot.user)
      throw new Error(
        'cannot register without bot user, as cannot get id without bot user.',
      );
    const allGuildCmds = commands.filter(v => !v.restrictedGuilds);
    const perGuildCmds = new Map<string, typeof commands>();
    commands
      .filter(v => !allGuildCmds.includes(v))
      .forEach(cmd =>
        cmd.restrictedGuilds?.forEach(guild =>
          perGuildCmds.set(guild, [...(perGuildCmds.get(guild) ?? []), cmd]),
        ),
      );
    const removeRestrictedField = <
      T extends {
        restrictedGuilds: any;
      },
    >(
      v: T,
    ): Omit<T, 'restrictedGuilds'> => {
      const v2 = structuredClone(v);
      delete v2.restrictedGuilds;
      return v2;
    };
    try {
      await Promise.all([
        (async () => {
          const guilds = await bot.guilds.fetch();
          await Promise.all(
            guilds.map(async guild => {
              const guildCommands = [
                ...(isDev ? allGuildCmds : []),
                ...(perGuildCmds.get(guild.id) ?? []),
              ].map(removeRestrictedField);
              logger.debug(
                `Updating ${commands.length} commands for guild [id=${guild.id}]`,
              );
              await registerCommandsInGuild(
                rest,
                guild.id,
                bot.user!.id,
                guildCommands,
              );
            }),
          );
        })(),
        !isDev
          ? registerCommandsGlobally(
              rest,
              bot.user!.id,
              allGuildCmds.map(removeRestrictedField),
            )
          : Promise.resolve(),
      ]);
      botConfig.lastCommandHash = commandHash;
    } catch (error) {
      logger.error(error, 'Error while re-registering commands!');
    }
  }
  await botConfig.save();
  logger.info(`Ready!`);
});
